package me.zhengjie.logging.service.impl

import cn.hutool.core.lang.Dict
import cn.hutool.core.util.ObjectUtil
import com.alibaba.fastjson.JSON
import jakarta.persistence.criteria.CriteriaBuilder
import jakarta.persistence.criteria.CriteriaQuery
import jakarta.persistence.criteria.Root
import jakarta.servlet.http.HttpServletResponse
import me.zhengjie.common.utils.*
import me.zhengjie.common.utils.FileUtil.downloadExcel
import me.zhengjie.logging.domain.SysLog
import me.zhengjie.logging.repository.LogRepository
import me.zhengjie.logging.service.SysLogService
import me.zhengjie.logging.service.dto.SysLogQueryCriteria
import me.zhengjie.logging.service.mapper.LogErrorMapper
import me.zhengjie.logging.service.mapper.LogSmallMapper
import org.aspectj.lang.ProceedingJoinPoint
import org.aspectj.lang.reflect.MethodSignature
import org.springframework.data.domain.Pageable
import org.springframework.data.jpa.domain.Specification
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Propagation
import org.springframework.transaction.annotation.Transactional
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestParam
import org.springframework.web.multipart.MultipartFile
import java.io.IOException
import java.lang.reflect.Method

/**
 * @author Kuki Wu
 * @date 2024-11-24
 */
@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true, rollbackFor = [Exception::class])
class SysLogServiceImpl(
    private val logRepository: LogRepository,
    private val logErrorMapper: LogErrorMapper,
    private val logSmallMapper: LogSmallMapper
) : SysLogService {
    override fun queryAll(criteria: SysLogQueryCriteria, pageable: Pageable?): Any? {
        val page =
            logRepository.findAll(Specification { root: Root<SysLog?>, criteriaQuery: CriteriaQuery<*>?, cb: CriteriaBuilder ->
                QueryHelp.getPredicate(
                    root,
                    criteria,
                    cb
                )
            }, pageable!!)
        return if ("ERROR" == criteria.logType) {
            PageUtil.toPage(page.map { entity: SysLog? -> logErrorMapper.toDto(entity) })
        } else page
    }

    override fun queryAll(criteria: SysLogQueryCriteria?): List<SysLog?> {
        return logRepository.findAll { root: Root<SysLog?>, criteriaQuery: CriteriaQuery<*>?, cb: CriteriaBuilder ->
            QueryHelp.getPredicate(
                root,
                criteria,
                cb
            )
        }
    }

    override fun queryAllByUser(criteria: SysLogQueryCriteria, pageable: Pageable?): Any? {
        val page =
            logRepository.findAll(Specification { root: Root<SysLog>, criteriaQuery: CriteriaQuery<*>?, cb: CriteriaBuilder ->
                QueryHelp.getPredicate(
                    root,
                    criteria,
                    cb
                )
            }, pageable!!)
        return PageUtil.toPage(page.map { entity: SysLog? -> logSmallMapper.toDto(entity) })
    }

    override fun queryAllByUser(criteria: SysLogQueryCriteria): List<SysLog?> {
        return logRepository.findAll { root: Root<SysLog?>, criteriaQuery: CriteriaQuery<*>?, cb: CriteriaBuilder ->
                QueryHelp.getPredicate(
                    root,
                    criteria,
                    cb
                )
        }
    }

    @Transactional(rollbackFor = [Exception::class])
    override fun save(username: String?, browser: String?, ip: String?, joinPoint: ProceedingJoinPoint, syslog: SysLog) {
        var username = username
        val signature = joinPoint.signature as MethodSignature
        val method = signature.method
        val aopLog = method.getAnnotation(me.zhengjie.logging.annotation.Log::class.java)

        // 方法路径
        val methodName = joinPoint.target.javaClass.name + "." + signature.name + "()"
        // 描述
        if (syslog != null) {
            syslog.description = aopLog.value
        }

        syslog.requestIp = ip
        syslog.address = StringUtils.getCityInfo(syslog.requestIp)
        syslog.method = methodName
        syslog.username = username
        syslog.params= getParameter(method, joinPoint.args)

        // 记录登录用户，隐藏密码信息
        if (signature.name == "login" && org .apache.commons.lang3.StringUtils.isNotEmpty(syslog.params)) {
            val obj = JacksonUtil.parseJSONObject(syslog.params)
            syslog.username = obj!!["username"].toString() //使用jackjson
            syslog.params = JacksonUtil.toJSONString(Dict.create().set("username", syslog.username))
        }

        syslog.browser = browser

        logRepository.save(syslog)
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private fun getParameter(method: Method, args: Array<Any>): String? {
        val argList: MutableList<Any> = java.util.ArrayList()
        val parameters = method.parameters
        for (i in parameters.indices) {
            // 过滤掉不能序列化的类型: MultiPartFile
            if (args[i] is MultipartFile) {
                continue
            }
            //将RequestBody注解修饰的参数作为请求参数
            val requestBody = parameters[i].getAnnotation(
                RequestBody::class.java
            )
            if (requestBody != null) {
                argList.add(args[i])
            }
            //将RequestParam注解修饰的参数作为请求参数
            val requestParam = parameters[i].getAnnotation(RequestParam::class.java)
            if (requestParam != null) {
                val map: MutableMap<String, Any> = HashMap(2)
                var key = parameters[i].name
                if (!org.apache.commons.lang3.StringUtils.isEmpty(requestParam.value)) {
                    key = requestParam.value
                }
                map[key] = args[i]
                argList.add(map)
            }
        }
        if (argList.isEmpty()) {
            return ""
        }
        return if (argList.size == 1) JSON.toJSONString(argList[0]) else JSON.toJSONString(argList)
    }
    override fun findByErrDetail(id: Long?): Any? {
        val details = logRepository.findExceptionById(id).exceptionDetail
        return Dict.create()
            .set("exception", String(if (ObjectUtil.isNotNull(details)) details else "".toByteArray()))
    }

    @Throws(IOException::class)
    override fun download(logs: List<SysLog?>?, response: HttpServletResponse?) {
        val list: MutableList<Map<String?, Any?>?> = ArrayList()
        for (log in logs!!) {
            val map: MutableMap<String?, Any?> = LinkedHashMap()
            map["用户名"] = log!!.username
            map["IP"] = log.requestIp
            map["IP来源"] = log.address
            map["描述"] = log.description
            map["浏览器"] = log.browser
            map["请求耗时/毫秒"] = log.time
            map["异常详情"] = String(if (ObjectUtil.isNotNull(log.exceptionDetail)) log.exceptionDetail else "".toByteArray())
            map["创建日期"] = log.createTime
            list.add(map)
        }
        downloadExcel(list, response!!)
    }

    @Transactional(rollbackFor = [java.lang.Exception::class])
    override fun delAllByError() {
        logRepository.deleteByLogType("ERROR")
    }

    @Transactional(rollbackFor = [java.lang.Exception::class])
    override fun delAllByInfo() {
        logRepository.deleteByLogType("INFO")
    }
}